package cn.zhoujing.demo.redis.service.impl;

import cn.zhoujing.demo.redis.common.constant.RedisConst;
import cn.zhoujing.demo.redis.common.dto.CheckTokenDTO;
import cn.zhoujing.demo.redis.common.dto.LoginDTO;
import cn.zhoujing.demo.redis.common.dto.LogoutDTO;
import cn.zhoujing.demo.redis.common.dto.UserInfoDTO;
import cn.zhoujing.demo.redis.service.LoginService;
import cn.zhoujing.demo.redis.service.factory.GrantTypeFactory;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.security.oauth2.provider.endpoint.CheckTokenEndpoint;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Service;
import org.springframework.web.HttpRequestMethodNotSupportedException;

import java.util.ArrayList;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author zhoujing
 * @createTime 2023/12/1 - 18:12
 */
@Slf4j
@Service
public class LoginServiceImpl implements LoginService {


    @Autowired
    private Map<String, GrantTypeFactory> grantTypeFactoryMap;

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private CheckTokenEndpoint checkTokenEndpoint;

    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public OAuth2AccessToken postAccessToken(LoginDTO dto) throws HttpRequestMethodNotSupportedException, JsonProcessingException {
        dto.isEmptyCheck();
        User clientUser = new User(dto.getClientId(), dto.getClientSecret(), new ArrayList<>());
        //生成已经认证的client
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(clientUser, dto.getClientSecret(), new ArrayList<>());
        GrantTypeFactory grantTypeFactory = grantTypeFactoryMap.get(dto.getGrantType());
        OAuth2AccessToken accountToken = grantTypeFactory.getAccountToken(dto, token);
        // 登录成功后，写入缓存
        this.writeCache(accountToken);
        return accountToken;
    }

    @Override
    public boolean removeAccessToken(LogoutDTO dto) {
        OAuth2AccessToken accessToken = tokenStore.readAccessToken(dto.getAccountToken());
        if (Objects.nonNull(accessToken)) {
            // 从缓存中删除
            this.clearCache(accessToken);
            OAuth2RefreshToken refreshToken = accessToken.getRefreshToken();
            // 从TokenStore中移除
            tokenStore.removeAccessToken(accessToken);
            tokenStore.removeRefreshToken(refreshToken);
            tokenStore.removeAccessTokenUsingRefreshToken(refreshToken);
        } else {
            log.info("token已失效");
        }
        return true;
    }

    /**
     * 将用户信息写入缓存
     *
     * @param accessToken 用户的Account_Key
     * @throws JsonProcessingException JSON转String异常
     */
    private void writeCache(OAuth2AccessToken accessToken) throws JsonProcessingException {
        Map<String, ?> checkToken = checkTokenEndpoint.checkToken(accessToken.getValue());
        CheckTokenDTO checkTokenDTO = objectMapper.convertValue(checkToken, CheckTokenDTO.class);
        String userInfoKey = this.getCacheKey(checkTokenDTO);
        UserInfoDTO data = UserInfoDTO.builder()
                .userId(checkTokenDTO.getUser_name())
                .userName("小明").checkToken(checkTokenDTO)
                .accessToken(accessToken).build();
        stringRedisTemplate.opsForValue().set(userInfoKey, objectMapper.writeValueAsString(data), accessToken.getExpiresIn(), TimeUnit.SECONDS);
    }

    /**
     * 清除用户在缓存中的数据
     *
     * @param accessToken 用户的Account_Key
     */
    private void clearCache(OAuth2AccessToken accessToken) {
        Map<String, ?> checkToken = checkTokenEndpoint.checkToken(accessToken.getValue());
        CheckTokenDTO checkTokenDTO = objectMapper.convertValue(checkToken, CheckTokenDTO.class);
        String userInfoKey = this.getCacheKey(checkTokenDTO);
        stringRedisTemplate.delete(userInfoKey);
    }

    /**
     * 获取缓存key
     *
     * @param checkTokenDTO 检验的tokenDTO
     * @return 用户在缓存中的key
     */
    private String getCacheKey(CheckTokenDTO checkTokenDTO) {
        String clientId = checkTokenDTO.getClient_id();
        String userId = checkTokenDTO.getUser_name();
        CharSequence lastId = userId.subSequence(userId.length() - 1, userId.length());
        String userInfoKey = String.format(RedisConst.USER_INFO, clientId, lastId, userId);
        return userInfoKey;
    }

}
